
#include "samplers.fx"

#pragma warning( disable : 4000 )

struct Light
{
	float4 diffuse;		// diffuse.w = exponent (for spotlights)
	float4 specular;	// specular.w = cutoff angle (for spotlights)
	float4 direction;	//
	float4 position;		
	float4 attenuation;	// attenuation.w = 0 / directional
};						// attenuation.w = 1 / spotlight

struct Material
{
	float4 ambient;
	float4 diffuse;
	float4 specular;
	float4 vIsTextured;	// x:diffuse, y:specular, z:normal
};

Material mat_params;

Texture2D	tex_diffuse;
Texture2D	tex_specular;
Texture2D	tex_normal;

Light lights[4];
Texture2D tex_shadow_map[4];
float4x4 m_L_ViewProj[4];

float3 pEye;


bool bUsePCSS;
bool bShadowed;
bool bLit;
uint num_lights;
int shadow_size;



float shadow_PCSS(float3 wpos, uint i)
{
	float4 projected_pos = mul(float4(wpos, 1.0f), m_L_ViewProj[i]);
	projected_pos /= projected_pos.w;
	projected_pos.xy = projected_pos.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);

	if (projected_pos.x > 1.0f || projected_pos.x < 0.0f ||
		projected_pos.y > 1.0f || projected_pos.y < 0.0f)
		return 1.0f;

	float step = 1/(float)shadow_size;
	float l_size = 5.0f;
	float epsilon = 0.005f;
	float d_receiver = projected_pos.z;

	float d_blocker = 0.0f;
	uint  num_blockers = 0;

	uint kernel_dim = 4;
	float2 start = projected_pos.xy - float2(floor(kernel_dim/2) * step, floor(kernel_dim/2) * step); 

	
	for (uint i=0; i<kernel_dim; i++)
	{

		for (uint j=0; j<kernel_dim; j++)
		{
			float v = tex_shadow_map[0].SampleLevel(s_point, start + step * float2(i, j), 0).r;
			
			if (v + epsilon < d_receiver)
			{
				d_blocker += v;
				num_blockers++;
			}
		}

	}	
	
	if (num_blockers == 0)
		return 1.0f;
	else if (num_blockers == kernel_dim*kernel_dim)
		return 0.0f;
	
	d_blocker /= (float)num_blockers;

	float w = l_size * ((d_receiver - d_blocker) / d_receiver);

	w = (w*100.0f)/(float)30;
	uint sample_dim = (int)lerp(2, 8, w); 

	float a = floor(sample_dim/2) * step;
	start = projected_pos.xy - float2(a, a); 

	float res = 0.0f;

	for (i=0; i<sample_dim; i++)
	{
		for (uint j=0; j<sample_dim; j++)
		{
			float2 offset;
			offset = float2(i, j);
			
			float2 scaled_offset = step * offset;
			float2 coords = start + scaled_offset;

			float v = tex_shadow_map[0].SampleLevel(s_point, coords, 0).r;
			
			float r = d_receiver <= v + epsilon;

			res += r * (1/(float)(sample_dim*sample_dim)); 
		}

	}	


	return res;
}

float shadow_factor(float3 wpos, uint i)
{

	float4 projected_pos = mul(float4(wpos, 1.0f), m_L_ViewProj[i]);
	projected_pos /= projected_pos.w;
	projected_pos.xy = projected_pos.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);

	if (projected_pos.x > 1.0f || projected_pos.x < 0.0f ||
		projected_pos.y > 1.0f || projected_pos.y < 0.0f)
		return 1.0f;

	float step = 1/(float)shadow_size;
	float epsilon = 0.001f;
	
	float v0 = tex_shadow_map[i].Sample(s_linear, projected_pos.xy).r;
	float v1 = tex_shadow_map[i].Sample(s_linear, projected_pos.xy + float2(step, 0.0f)).r;
	float v2 = tex_shadow_map[i].Sample(s_linear, projected_pos.xy + float2(0.0f, step)).r;
	float v3 = tex_shadow_map[i].Sample(s_linear, projected_pos.xy + float2(step, step)).r;

	bool r0 = projected_pos.z <= v0 + epsilon;
	bool r1 = projected_pos.z <= v1 + epsilon;
	bool r2 = projected_pos.z <= v2 + epsilon;
	bool r3 = projected_pos.z <= v3 + epsilon;

	return r0;
}

float3 shading(float3 p, float3 N, uint i)
{

	float color = 0;

	float3 V = normalize(pEye - p);
	float3 L = -normalize(lights[i].direction.xyz);
	float3 H = normalize(L+V);
	
	float3 diffuse = mat_params.diffuse.xyz * lights[i].diffuse.xyz * saturate(dot(N, L));
	
	float3 specular = mat_params.specular.xyz * lights[i].specular.xyz;
	float s = pow(saturate(dot(H, L)), mat_params.specular.w);
	specular *= s;

	if (lights[i].attenuation.w == 1) // spotlight
	{
		// attenuation
		float d = length(lights[i].position.xyz - p);

		float att = lights[i].attenuation.x + 
					lights[i].attenuation.y * d +
					lights[i].attenuation.z * d * d;

		att = 1.0f / att;
		//

		// intensity based on angle between L and light's direction
		float3 a = -L;
		float3 b = normalize(p - lights[i].position.xyz).xyz;

		// cos of angle between L and light's direction vector
		float cos_angle = dot(a, b);
		// cos of cutoff angle
		float c_l = cos(lights[i].specular.w);
	
		if (cos_angle > c_l) 
			att *= pow(max(cos_angle, 0.0f), lights[i].diffuse.w);
		else
			att = 0.0f;
		//

		diffuse  *= att;
		specular *= att;
	}

	return diffuse +  specular;

}


float3 shade(float3 p, float3 N, uint i)
{

	float3 color = float3(0.0f, 0.0f, 0.0f);

	[unroll]
	for (uint i=0; i<num_lights; ++i) 
	{
		// get color contribution
		float3 light_contribution = shading(p, N, i);
		
		// color = color * shadow
		if (bShadowed) {
			float s;
		
			if (bUsePCSS)
				s = shadow_PCSS(p, i);
			else 
				s = shadow_factor(p, i);
		
			light_contribution *= s;
		}

		color += light_contribution;
	}

	return color;
}

